--[[
--
--	Chronos
--		Keeper of Time
--
--	By AnduinLothar, Alexander Brazie, and Thott 
--
--	Chronos manages time. You can schedule a function to be called
--	in X seconds, with or without an id. You can request a timer, 
--	which tracks the elapsed duration since the timer was started. 
--
--  To use as an embeddable addon:
--	- Put the Chronos folder inside your Interface/AddOns/<YourAddonName>/ folder.
--	- Add Chronos\Chronos.xml to your toc or load it in your xml before your localization files.
--	- Add Chronos to the OptionalDeps in your toc
--	
--	To use as an addon library:
--	- Put the Chronos folder inside your Interface/AddOns/ folder.
--	- Add Chronos to the Dependencies in your toc
--
--	Please see below or see http://www.wowwiki.com/Chronos_(addon) for details.
--
--	$LastChangedBy: karlkfi $
--	$Date: 2006-12-21 06:19:14 -0600 (Thu, 21 Dec 2006) $
--	$Rev: 4467 $
--	
--]]

local CHRONOS_REV = 2.12;

local isBetterInstanceLoaded = ( Chronos and Chronos.version and Chronos.version >= CHRONOS_REV );

if (not isBetterInstanceLoaded) then
	
	if (not Chronos) then
		Chronos = {};
	end
	
	Chronos.version = CHRONOS_REV;
	
	------------------------------------------------------------------------------
	--[[ Variables ]]--
	------------------------------------------------------------------------------
	
	Chronos.online = true;
	
	CHRONOS_DEBUG = false;
	CHRONOS_DEBUG_WARNINGS = false;

	-- Chronos Data
	if (not ChronosData) then
		ChronosData = {};
	end
	
	-- Chronos Recycled Tables Storage
	if (not Chronos.tables) then
		Chronos.tables = {};
	end
		
	-- Initialize the Timers
	if (not ChronosData.timers) then
		ChronosData.timers = {};
	end
	
	-- Initialize the perform-over-time task list
	if (not ChronosData.tasks) then
		ChronosData.tasks = {};
	end
		
	-- Maximum items per frame
	Chronos.MAX_TASKS_PER_FRAME = 100;
		
	-- Maximum steps per task
	Chronos.MAX_STEPS_PER_TASK = 300;
	
	-- Maximum time delay per frame
	Chronos.MAX_TIME_PER_STEP = .3;
		
	Chronos.emptyTable = {};
	
	------------------------------------------------------------------------------
	--[[ User Functions ]]--
	------------------------------------------------------------------------------
	
	--[[
	-- debug(boolean)
	-- 
	-- Toggles debug mode
	]]--
	function Chronos.debug(enable)
		if (enable) then
			ChronosFrame:SetScript("OnUpdate", Chronos.OnUpdate_Debug);
			CHRONOS_DEBUG = true;
			CHRONOS_DEBUG_WARNINGS = true;
		else
			ChronosFrame:SetScript("OnUpdate", Chronos.OnUpdate_Quick);
			CHRONOS_DEBUG = false;
			CHRONOS_DEBUG_WARNINGS = false;
		end
	end
	
	--[[
	-- Scheduling functions
	-- Parts rewritten by AnduinLothar for efficiency
	-- Parts rewritten by Thott for speed
	-- Written by Alexander
	-- Original by Thott
	--
	-- Usage: Chronos.schedule(when,handler,arg1,arg2,etc)
	--
	-- After <when> seconds pass (values less than one and fractional values are
	-- fine), handler is called with the specified arguments, i.e.:
	--	 handler(arg1,arg2,etc)
	--
	-- If you'd like to have something done every X seconds, reschedule
	-- it each time in the handler or preferably use scheduleRepeating.
	--
	-- Also, please note that there is a limit to the number of
	-- scheduled tasks that can be performed per xml object at the
	-- same time. 
	--]]
	function Chronos.schedule(when, handler, ...)
		if ( not Chronos.online ) then 
			return;
		end
		if ( not handler) then
			Chronos.printError("ERROR: nil handler passed to Chronos.schedule()");
			return;
		end
				
		--local memstart = collectgarbage("count");
		-- -- Assign an id
		-- local id = "";
		-- if ( not this ) then 
		-- 	id = "Keybinding";
		-- else
		-- 	id = this:GetName();
		-- end
		-- if ( not id ) then 
		-- 	id = "_DEFAULT";
		-- end
		-- if ( not when ) then 
		-- 	Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS", "Chronos Error Detection: ", id , " has sent no interval for this function. ", when );
		-- 	return;
		-- end

		-- -- Ensure we're not looping ChronosFrame
		-- if ( id == "ChronosFrame" and ChronosData.lastID ) then 
		-- 	id = ChronosData.lastID;
		-- end

		-- use recycled tables to avoid excessive garbage collection -AnduinLothar
		--tinsert(ChronosData.sched, Chronos.getTable())
		--local i = #ChronosData.sched
		local recTable = Chronos.getTable()
		-- ChronosData.sched[i].id = id;
		recTable.time = when + GetTime();
		recTable.handler = handler;
		recTable.args = Chronos.getArgTable(...);

		-- task list is a heap, add new
		local i = #ChronosData.sched+1
		while(i > 1) do
			if(recTable.time < ChronosData.sched[i-1].time) then
				i = i - 1;
			else
				break
			end
		end
		tinsert(ChronosData.sched, i, recTable)
		
		-- Debug print
		--Chronos.printDebugError("CHRONOS_DEBUG", "Scheduled "..handler.." in "..when.." seconds from "..id );
		--Chronos.printError("Memory change in schedule: "..memstart.."->"..memend.." = "..memend-memstart);
	end
	

	--[[
	--	Chronos.scheduleByName(name, delay, function, arg1, ... );
	--
	-- Same as Chronos.schedule, except it takes a schedule name argument.
	-- Only one event can be scheduled with a given name at any one time.
	-- Thus if one exists, and another one is scheduled, the first one
	-- is deleted, then the second one added.
	--
	--]]
	function Chronos.scheduleByName(name, when, handler, ...)
		if ( not name ) then 
			Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS","Chronos Error Detection: No name specified to Chronos.scheduleByName");
			return;
		end
		local namedSchedule = ChronosData.byName[name];
		if(namedSchedule and handler) then
			Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS","Chronos Error Detection: scheduleByName is reasigning \"".. name.."\".");
			Chronos.releaseTable(ChronosData.byName[name]);
		else
			if ( not handler ) then
				if ( not namedSchedule ) then
					Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS","Chronos Error Detection: No handler specified to Chronos.scheduleByName, no previous entry found for scheduled entry \"".. name.."\".");
					return;
				end
				if ( not namedSchedule.handler ) then
					Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS","Chronos Error Detection: No handler specified to Chronos.scheduleByName, no handler could be found in previous entry of \"".. name.."\" either.");
					return;
				end
				handler = namedSchedule.handler;
				Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS","Chronos: scheduleByName is updating \"".. name.."\" to time: ".. when);
			else
				Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS","Chronos: scheduleByName is asigning \"".. name.."\".");
			end
		end
		ChronosData.byName[name] = Chronos.getTable();
		namedSchedule = ChronosData.byName[name];
		namedSchedule.time = when+GetTime()
		namedSchedule.handler = handler;
		namedSchedule.args = Chronos.getArgTable(...);
	end

	--[[
	--	unscheduleByName(name);
	--
	--		Removes an entry that was created with scheduleByName()
	--
	--	Args:
	--		name - the name used
	--
	--]]
	function Chronos.unscheduleByName(name)
		if ( not Chronos.online ) then 
			return;
		end
		if ( not name ) then 
			Chronos.printError("No name specified to Chronos.unscheduleByName");
			return;
		end
		if(ChronosData.byName[name]) then
			Chronos.releaseTable(ChronosData.byName[name]);
			ChronosData.byName[name] = nil;
		end
		
		-- Debug print
		--Chronos.printDebugError("CHRONOS_DEBUG", "Cancelled scheduled timer of name ",name);
	end
	
	--[[
	--	unscheduleRepeating(name);
	--		Mirrors unscheduleByName for backwards compatibility
	--]]
	Chronos.unscheduleRepeating = Chronos.unscheduleByName;
	
	--[[
	--	isScheduledByName(name)
	--		Returns the amount of time left if it is indeed scheduled by name!
	--
	--	returns:
	--		number - time remaining
	--		nil - not scheduled
	--
	--]]
	function Chronos.isScheduledByName(name)
		if ( not Chronos.online ) then 
			return;
		end
		if ( not name ) then 
			Chronos.printError("No name specified to Chronos.isScheduledByName "..(this:GetName() or "unknown"));
			return;
		end
		local namedSchedule = ChronosData.byName[name];
		if(namedSchedule) then
			return namedSchedule.time - GetTime();
		end
		
		-- Debug print
		--Chronos.printDebugError("CHRONOS_DEBUG", "Did not find timer of name ",name);
		return nil;
	end
	
	--[[
	--	isScheduledRepeating(name)	
	--		Mirrors isScheduledByName for backwards compatibility
	--]]
	Chronos.isScheduledRepeating = Chronos.isScheduledByName;
	
	--[[
	--	Chronos.scheduleRepeating(name, delay, function);
	--
	-- Same as Chronos.scheduleByName, except it repeats without recalling and takes no arguments.
	--
	--]]
	function Chronos.scheduleRepeating(name, when, handler)
		if ( not name ) then 
			Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS","Chronos Error Detection: No name specified to Chronos.scheduleRepeating");
			return;
		end
		local namedSchedule = ChronosData.byName[name];
		if(namedSchedule and handler) then
			Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS","Chronos Error Detection: scheduleRepeating is reasigning ".. name);
			Chronos.releaseTable(ChronosData.byName[name]);
		else
			if ( not handler ) then
				if ( not namedSchedule ) then
					Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS","Chronos Error Detection: No handler specified to Chronos.scheduleRepeating, no previous entry found for scheduled entry '".. name.."'.");
					return;
				end
				if ( not namedSchedule.handler ) then
					Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS","Chronos Error Detection: No handler specified to Chronos.scheduleRepeating, no handler could be found in previous entry '".. name.."' either.");
					return;
				end
				handler = namedSchedule.handler;
				Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS","Chronos: scheduleRepeating is updating '".. name.."' to time: ".. when);
			else
				Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS","Chronos: scheduleRepeating is asigning '".. name.."'.");
			end
		end
		ChronosData.byName[name] = Chronos.getTable();
		namedSchedule = ChronosData.byName[name];
		namedSchedule.time = when+GetTime();
		namedSchedule.period = when;
		namedSchedule.handler = handler;
		namedSchedule.repeating = true;
	end
	
	--[[
	--	Chronos.flushByName(name, when);
	--
	-- Updates the ByName or Repeating event to flush at the time specified.  If no time is specified flush will be immediate. If it is a Repeating event the timer will be reset.
	--
	--]]
	function Chronos.flushByName(name, when)
		if ( not name ) then 
			Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS","Chronos Error Detection: No name specified to Chronos.flushByName");
			return;
		elseif ( not ChronosData.byName[name] ) then
			Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS","Chronos Error Detection: no previous entry found for Chronos.flushByName entry '".. name.."'.");
			return;
		end
		if (not when) then
			Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS","Chronos: flushing '".. name.."'.");
			when = GetTime();
		else
			Chronos.printDebugError("CHRONOS_DEBUG_WARNINGS","Chronos: flushing '".. name.."' in "..when.." seconds.");
			when = when+GetTime();
		end
		ChronosData.byName[name].time = when;
	end

	--[[
	--	Chronos.startTimer([ID]);
	--		Starts a timer on a particular
	--
	--	Args
	--		ID - optional parameter to identify who is asking for a timer.
	--		
	--		If ID does not exist, this:GetName() is used. 
	--
	--	When you want to get the amount of time passed since startTimer(ID) is called, 
	--	call getTimer(ID) and it will return the number in seconds. 
	--
	--]]
	function Chronos.startTimer( id ) 
		if ( not Chronos.online ) then 
			return;
		end

		if ( not id ) then 
			id = this:GetName();
		end

		-- Create a table for this id's timers
		if ( not ChronosData.timers[id] ) then
			ChronosData.timers[id] = Chronos.getTable();
		end

		-- Clear out an entry if the table is too big.
		if (#ChronosData.timers[id] > Chronos.MAX_TASKS_PER_FRAME) then
			Chronos.printError("Too many Chronos timers created for id " .. tostring(id));
			return;
		end

		-- Add a new timer entry 
		table.insert(ChronosData.timers[id], GetTime());
	end


	--[[
	--	endTimer([id]);
	-- 
	--		Ends the timer and returns the amount of time passed.
	--
	--	args:
	--		id - ID for the timer. If not specified, then ID will
	--		be this:GetName()
	--
	--	returns:
	--		(Number delta, Number start, Number end)
	--
	--		delta - the amount of time passed in seconds.
	--		start - the starting time 
	--		now - the time the endTimer was called.
	--]]

	function Chronos.endTimer( id ) 
		if ( not Chronos.online ) then 
			return;
		end

		if ( not id ) then 
			id = this:GetName();
		end

		if ( not ChronosData.timers[id] or #ChronosData.timers[id] == 0) then
			return nil;
		end
	
		local now = GetTime();

		-- Grab the last timer called
		local startTime = tremove(ChronosData.timers[id]);

		return (now - startTime), startTime, now;
	end


	--[[
	--	getTimer([id]);
	-- 
	--		Gets the timer and returns the amount of time passed.
	--		Does not terminate the timer.
	--
	--	args:
	--		id - ID for the timer. If not specified, then ID will
	--		be this:GetName()
	--
	--	returns:
	--		(Number delta, Number start, Number end)
	--
	--		delta - the amount of time passed in seconds.
	--		start - the starting time 
	--		now - the time the endTimer was called.
	--]]

	function Chronos.getTimer( id ) 
		if ( not Chronos.online ) then 
			return;
		end

		if ( not id ) then 
			id = this:GetName();
		end

		local now = GetTime();
		if ( not ChronosData.timers[id] or #ChronosData.timers[id] == 0) then
			return 0, 0, now;
		end
	
		-- Grab the last timer called
		local startTime = ChronosData.timers[id][#ChronosData.timers[id]];

		return (now - startTime), startTime, now;
	end
	
	--[[
	--	isTimerActive([id])
	--		returns true if the timer exists. 
	--		
	--	args:
	--		id - ID for the timer. If not specified, then ID will
	--		be this:GetName()
	--
	--	returns:
	--		true - exists
	--		false - does not
	--]]
	function Chronos.isTimerActive( id ) 
		if ( not Chronos.online ) then 
			return;
		end

		if ( not id ) then 
			id = this:GetName();
		end

		-- Create a table for this id's timers
		if ( not ChronosData.timers[id] ) then
			return false;
		end

		return true;
	end

	--[[
	--	getTime()
	--
	--		returns the Chronos internal elapsed time.
	--
	--	returns:
	--		(elapsedTime)
	--		
	--		elapsedTime - time in seconds since Chronos initialized
	--]]	
	function Chronos.getTime() 
		return ChronosData.elapsedTime;
	end
	
	--[[
	--	Chronos.afterInit(func, ...)
	--		Performs func after the game has truely started.
	--	By Thott
	--]]
	function Chronos.afterInit(func, ...)
		local id;
		if(this) then
			id = this:GetName();
		else
			id = "unknown";
		end
		--if(id == "SkyFrame") then
		--	Chronos.printError("Ignoring Sky init");
		--	return;
		--end
		if(ChronosData.initialized) then
			func(...);
		else
			if(not ChronosData.afterInit) then
				ChronosData.afterInit = Chronos.getTable();
				Chronos.schedule(0.2, Chronos.initCheck);
			end
			local recTable = Chronos.getTable();
			recTable.func = func;
			recTable.args = Chronos.getArgTable(...);
			recTable.id = id;
			tinsert(ChronosData.afterInit, recTable);
		end
	end
	
	
	------------------------------------------------------------------------------
	--[[ Table Recycling ]]--
	------------------------------------------------------------------------------
	
	function Chronos.getTable(...)
		local stack = Chronos.tables;
		if (not stack) then
			Chronos.tables = {};
			stack = Chronos.tables;
			return {};
		end
		local recTable;
		if (#stack >= 1) then
			recTable = tremove(stack)
		else
			recTable = {};
		end
		for i=1, select("#", ...) do
			recTable[i] = select(i, ...);
		end
		return recTable;
	end
	
	-- Release a table to be nilled and used again.
	-- Optionally pass in an unpack(...) as the 2nd arg so that you can return the args:
	-- return Chronos.releaseTable(t1, unpack(t1))
	function Chronos.releaseTable(t1, ...)
		if (type(t1) ~= "table") then
			return;
		end
		
		local stack = Chronos.tables;
		if (not stack) then
			Chronos.tables = {};
			stack = Chronos.tables;
		end
		
		for k,v in pairs(t1) do
			t1[k] = nil;
		end
		
		tinsert(stack, t1);
		return ...;
	end
	
	
	------------------------------------------------------------------------------
	--[[ Helpers Functions ]]--
	------------------------------------------------------------------------------
	
	function Chronos.getArgTable(...)
		if (select('#', ...) == 0) then
			return Chronos.emptyTable;
		else
			return Chronos.getTable(...);
		end
	end
	
	function Chronos.run(func,args)
		if(func) then
			if(args) then
				return func(unpack(args));
			else
				return func();
			end
		end
	end
	
	function Chronos.printError(text)
		ChatFrame1:AddMessage(text, RED_FONT_COLOR.r, RED_FONT_COLOR.g, RED_FONT_COLOR.b, 1.0, UIERRORS_HOLD_TIME);
	end
	
	function Chronos.printDebugError(var, text)
		if (var) and (getglobal(var)) then
			Chronos.printError(text);
		end
	end
	
	------------------------------------------------------------------------------
	--[[ Frame Script Helpers ]]--
	------------------------------------------------------------------------------
	
	function Chronos.chatColorsInit()
		ChronosData.chatColorsInitialized = true;
		ChronosFrame:UnregisterEvent("UPDATE_CHAT_COLOR");
	end
	
	function Chronos.initCheck()
		if(not ChronosData.initialized) then
			if(UnitName("player") and UnitName("player")~=UKNOWNBEING and UnitName("player")~=UNKNOWNBEING and UnitName("player")~=UNKNOWNOBJECT and ChronosData.variablesLoaded and ChronosData.enteredWorld and ChronosData.chatColorsInitialized) then
				ChronosData.initialized = true;
				Chronos.schedule(1,Chronos.initCheck); 
				return;
			else
				Chronos.schedule(0.2,Chronos.initCheck); 
				return;
			end
		end
		if(ChronosData.afterInit) then
			local i = ChronosData.afterInit_i;
			if(not i) then
				i = 1;
			end
			ChronosData.afterInit_i = i+1;
			--Chronos.printError("afterInit: processing ",i," of ",ChronosData.afterInit.n," initialization functions, id: ",ChronosData.afterInit[i].id);
			Chronos.run(ChronosData.afterInit[i].func, ChronosData.afterInit[i].args);
			if(i == #ChronosData.afterInit) then
				for i,v in ipairs(ChronosData.afterInit) do
					Chronos.releaseTable(v);
				end
				Chronos.releaseTable(ChronosData.afterInit);
				ChronosData.afterInit = nil;
				ChronosData.afterInit_i = nil;
			else
				Chronos.schedule(0.1, Chronos.initCheck);
				return;
			end
		end
	end
	
	--[[
	--	Sends a chat command through the standard editbox
	--]]
	function Chronos.SendChatCommand(command)
		local text = ChatFrameEditBox:GetText();
		ChatFrameEditBox:SetText(command);
		ChatEdit_SendText(ChatFrameEditBox);
		ChatFrameEditBox:SetText(text);
	end
	
	function Chronos.RegisterSlashCommands()
		--Needs to be able Variables load if you want to use Sky
		local chronosFunc = function(msg)
			local _,_,seconds,command = string.find(msg,"([%d\.]+)%s+(.*)");
			if(seconds and command) then
				Chronos.schedule(seconds,Chronos.SendChatCommand,command);
			else
				Chronos.printError(SCHEDULE_USAGE1);
				Chronos.printError(SCHEDULE_USAGE2);
			end
		end
		if (Satellite) then
			Satellite.registerSlashCommand(
				{
					id = "Schedule";
					commands = SCHEDULE_COMM;
					onExecute = chronosFunc;
					helpText = SCHEDULE_DESC;
					replace = true;
				}
			);
		else
			SlashCmdList["CHRONOS_SCHEDULE"] = chronosFunc;
			for i = 1, #SCHEDULE_COMM do setglobal("SLASH_CHRONOS_SCHEDULE"..i, SCHEDULE_COMM[i]); end
		end
	end
	
	------------------------------------------------------------------------------
	--[[ Frame Scripts ]]--
	------------------------------------------------------------------------------
	
	function Chronos.OnLoad()
		Chronos.framecount = 0;
		
		if (not ChronosData.byName) then
			ChronosData.byName = {};
		end
		if (not ChronosData.repeating) then
			ChronosData.repeating = {};
		end
		if (not ChronosData.sched) then
			ChronosData.sched = {};
		end
		ChronosData.elapsedTime = 0;
		
		Chronos.afterInit(Chronos.RegisterSlashCommands);
	end
	
	function Chronos.OnEvent()
		if(event == "VARIABLES_LOADED") then
			ChronosData.variablesLoaded = true;
			ChronosFrame:Show();
		elseif (event == "PLAYER_ENTERING_WORLD") then
			ChronosData.enteredWorld = true;
			Chronos.online = true;
		elseif (event == "PLAYER_LEAVING_WORLD") then
			Chronos.online = false;
		elseif ( event == "UPDATE_CHAT_COLOR" ) then
			Chronos.scheduleByName("ChronosAfterChatColorInit", 1, Chronos.chatColorsInit);
		end	
	end
	
	function Chronos.OnUpdate_Quick()
		if ( not Chronos.online ) then 
			return;
		end
		if ( not ChronosData.variablesLoaded ) then 
			return;
		end
		
		if ( ChronosData.elapsedTime ) then
			ChronosData.elapsedTime = ChronosData.elapsedTime + arg1;
		else
			ChronosData.elapsedTime = arg1;
		end
		
		-- Execute scheduled tasks that are ready, pulling them off the front of the list queue.
		local now = GetTime();
		local i;
		local task;
		while(#ChronosData.sched > 0) do
			if (not ChronosData.sched[1].time) then
				--Sea.io.printTable(ChronosData.sched[1]);
				tremove(ChronosData.sched, 1);
			elseif(ChronosData.sched[1].time <= now) then
				task = tremove(ChronosData.sched, 1);
				Chronos.run(task.handler, task.args);
				Chronos.releaseTable(task);
			else
				break;
			end
		end
		
		-- Execute named scheduled tasks that are ready.
		local k,v = next(ChronosData.byName);
		local newK, newV;
		while (k ~= nil) do
			newK,newV = next(ChronosData.byName, k);
			if (not v.time) then
				--Sea.io.printTable(v);
				ChronosData.byName[k] = nil;
			elseif (v.time <= now) then
				if (v.repeating) then
					ChronosData.byName[k].time = now + v.period;
					v.handler();
				else
					Chronos.run(v.handler, v.args);
					Chronos.releaseTable(ChronosData.byName[k]);
					ChronosData.byName[k] = nil;
				end
			end
			k,v = newK,newV;
		end
	end
	
	function Chronos.OnUpdate_Debug()
		if ( not Chronos.online ) then 
			return;
		end
		if ( not ChronosData.variablesLoaded ) then 
			return;
		end
		local memstart = collectgarbage("count");
		
		if ( ChronosData.elapsedTime ) then
			ChronosData.elapsedTime = ChronosData.elapsedTime + arg1;
		else
			ChronosData.elapsedTime = arg1;
		end
	
		local now = GetTime();
		local i;
		local task;
		-- Execute scheduled tasks that are ready, popping them off the heap.
		while(#ChronosData.sched > 0) do
			if(ChronosData.sched[1].time <= now) then
				task = tremove(ChronosData.sched, 1);
				Chronos.run(task.handler, task.args);
				Chronos.releaseTable(task);
			else
				break;
			end
		end
		
		local memend = collectgarbage("count");
		if(memend - memstart > 0) then
			Chronos.printError("gcmemleak from ChronosData.sched in OnUpdate: "..(memend - memstart));
		end
		
		-- Execute named scheduled tasks that are ready.
		memstart = memend;
		local k,v = next(ChronosData.byName);
		local newK, newV;
		while (k ~= nil) do
			newK,newV = next(ChronosData.byName, k);
			if(v.time <= now) then
				local m = collectgarbage("count");
				if (v.repeating) then
					ChronosData.byName[k].time = now + v.period;
					v.handler();
				else
					Chronos.run(v.handler, v.args);
					Chronos.releaseTable(ChronosData.byName[k]);
					ChronosData.byName[k] = nil;
				end
				local mm = collectgarbage("count");
				memstart = memstart + mm - m;
			end
			k,v = newK,newV;
		end
		
		memend = collectgarbage("count");
		if(memend - memstart > 0) then
			Chronos.printError("gcmemleak from ChronosData.byName in OnUpdate: "..(memend - memstart));
		end
	end
	
	------------------------------------------------------------------------------
	--[[ Frame Script Assignment ]]--
	------------------------------------------------------------------------------
	
	--Event Driver
	if (not ChronosFrame) then
		CreateFrame("Frame", "ChronosFrame");
	end
	ChronosFrame:Hide();
	--Event Registration
	ChronosFrame:RegisterEvent("VARIABLES_LOADED");
	ChronosFrame:RegisterEvent("PLAYER_ENTERING_WORLD");
	ChronosFrame:RegisterEvent("PLAYER_LEAVING_WORLD");
	ChronosFrame:RegisterEvent("UPDATE_CHAT_COLOR");
	--Frame Scripts
	ChronosFrame:SetScript("OnEvent", Chronos.OnEvent);
	ChronosFrame:SetScript("OnUpdate", Chronos.OnUpdate_Quick);
	--OnLoad Call
	Chronos.OnLoad();
	
end

